package vitaliy.bondar.diamondcave;

import java.io.IOException;

import org.andengine.engine.camera.hud.HUD;
import org.andengine.engine.camera.hud.controls.AnalogOnScreenControl;
import org.andengine.engine.camera.hud.controls.AnalogOnScreenControl.IAnalogOnScreenControlListener;
import org.andengine.engine.camera.hud.controls.BaseOnScreenControl;
import org.andengine.engine.handler.timer.ITimerCallback;
import org.andengine.engine.handler.timer.TimerHandler;
import org.andengine.entity.IEntity;
import org.andengine.entity.scene.background.Background;
import org.andengine.entity.text.Text;
import org.andengine.entity.text.TextOptions;
import org.andengine.util.HorizontalAlign;
import org.andengine.util.SAXUtils;
import org.andengine.util.color.Color;
import org.andengine.util.level.IEntityLoader;
import org.andengine.util.level.LevelLoader;
import org.andengine.util.level.constants.LevelConstants;
import org.xml.sax.Attributes;

import vitaliy.bondar.diamondcave.SceneManager.SceneType;
import android.content.SharedPreferences.Editor;
import android.opengl.GLES20;
import android.util.DisplayMetrics;

public class GameScene extends BaseScene implements Constants
{

    private HUD gameHUD;
    private Text levelNumberText;
    private int jewelsLeft;
    private AnalogOnScreenControl onScreenControl;

    private static final String TAG_ENTITY = "entity";
    private static final String TAG_ENTITY_ATTRIBUTE_X = "x";
    private static final String TAG_ENTITY_ATTRIBUTE_Y = "y";
    private static final String TAG_ENTITY_ATTRIBUTE_TYPE = "type";

    private static final Object TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_STONE = "stone";
    private static final Object TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_WALL = "wall";
    private static final Object TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_JEWEL = "jewel";
    private static final Object TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_GROUND = "ground";
    private static final Object TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_ENEMY = "enemy";
    private static final Object TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_PLAYER = "player";

    private Player player;
    private int level = -1;
    private GameObject[][] objectMap;
    
    @Override
    public void createScene()
    {
        createBackground();
        createHUD();
        createControl();
    }

    @Override
    public void onBackKeyPressed()
    {
        SceneManager.getInstance().loadSelectorScene(engine);
    }

    @Override
    public SceneType getSceneType()
    {
        return SceneType.SCENE_GAME;
    }

    private void disposeLevel()
    {
        camera.setBounds(0, 0, 800, 480);
        camera.setCenter(400, 240);
        camera.setHUD(null);
        camera.setChaseEntity(null);
        if (onScreenControl != null) onScreenControl.setVisible(false);
        activity.runOnUpdateThread(new Runnable()
        {
            @Override
            public void run()
            {
                setChildrenIgnoreUpdate(true);
                setIgnoreUpdate(true);
                detachChildren();

                onScreenControl = null;
                player = null;
                objectMap = null;
                levelNumberText = null;
            }
        });
    }
    
    @Override
    public void disposeScene()
    {
        disposeLevel();
        ResourcesManager.getInstance().unloadGameResources();
    }

    private void createBackground()
    {
        setBackground(new Background(Color.BLACK));
    }

    private void createHUD()
    {
        gameHUD = new HUD();

        levelNumberText = new Text(0, 0, resourcesManager.font, 
                activity.getString(R.string.level) + ": 0123456789",
                new TextOptions(HorizontalAlign.RIGHT), vbom);
        levelNumberText.setText(activity.getString(R.string.level) + ": 1");
        gameHUD.attachChild(levelNumberText);

        camera.setHUD(gameHUD);
    }

    private void createControl()
    {
        DisplayMetrics metrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);

        this.onScreenControl = new AnalogOnScreenControl(50, camera.getHeight()
                - (int)(1.3 * metrics.ydpi), camera, resourcesManager.control_region,
                resourcesManager.control_knob_region, 0.05f, vbom,
                new IAnalogOnScreenControlListener()
                {
                    @Override
                    public void onControlChange(final BaseOnScreenControl pBaseOnScreenControl,
                            final float pValueX, final float pValueY)
                    {
                        if (pValueX == 0 && pValueY == 0) return;
                        if (jewelsLeft == 0) return;
                        final float eps = 0.3f;
                        float x = pValueX;
                        float y = pValueY;
                        if (Math.abs(x) > eps && Math.abs(y) > eps)
                        {
                            if (Math.abs(x) > Math.abs(y))
                            {
                                y = 0f;
                            }
                            else
                            {
                                x = 0f;
                            }
                        }

                        Direction direction;

                        if (x > eps)
                        {
                            direction = Direction.RIGHT;
                        }
                        else if (x < -eps)
                        {
                            direction = Direction.LEFT;
                        }
                        else if (y > eps)
                        {
                            direction = Direction.DOWN;
                        }
                        else if (y < -eps)
                        {
                            direction = Direction.UP;
                        }
                        else return;

                        tryMove(direction);
                    }

                    @Override
                    public void onControlClick(final AnalogOnScreenControl pAnalogOnScreenControl)
                    {}
                });

        this.onScreenControl.getControlBase().setBlendFunction(GLES20.GL_SRC_ALPHA,
                GLES20.GL_ONE_MINUS_SRC_ALPHA);
        this.onScreenControl.getControlBase().setAlpha(0.5f);

        this.onScreenControl.getControlBase().setSize((int)(0.8 * metrics.xdpi),
                (int)(0.8 * metrics.ydpi));
        this.onScreenControl.getControlBase().setScale(1.25f);
        this.onScreenControl.getControlKnob().setScale(1.25f);
        this.onScreenControl.refreshControlKnobPosition();

        this.setChildScene(this.onScreenControl);
    }

    private void tryMove(Direction direction)
    {
        if (!player.tryMove(direction)) return;
        player.move(direction);
    }

    public GameObject findItemAtPosition(int x, int y)
    {
        return objectMap[y][x];
    }
    
    public void setItemInPosition(int x, int y, GameObject object)
    {
    	objectMap[y][x] = object;
    }
    
    public final int sceneSizeX()
    {
    	return objectMap[0].length;
    }
    
    public final int sceneSizeY()
    {
    	return objectMap.length;
    }
    
    public final int playerX()
    {
    	return player.cellX();
    }
    
    public final int playerY()
    {
    	return player.cellY();
    }

    public void loadLevel(int levelNumber)
    {
        this.level = levelNumber;
        SceneManager.getInstance().setCurrentLevel(levelNumber);
        levelNumberText.setText(activity.getString(R.string.level) + ": " + level);
        final LevelLoader levelLoader = new LevelLoader();
        levelLoader.setAssetBasePath("level/");

        levelLoader.registerEntityLoader(LevelConstants.TAG_LEVEL, new IEntityLoader()
        {
            @Override
            public IEntity onLoadEntity(final String pEntityName, final Attributes pAttributes)
            {
                final int width = SAXUtils.getIntAttributeOrThrow(pAttributes,
                        LevelConstants.TAG_LEVEL_ATTRIBUTE_WIDTH);
                final int height = SAXUtils.getIntAttributeOrThrow(pAttributes,
                        LevelConstants.TAG_LEVEL_ATTRIBUTE_HEIGHT);
                camera.setBounds(-350, 0, width * CELL_WIDTH, height * CELL_HEIGHT); // here we set
                                                                                     // camera
                                                                                     // bounds
                camera.setBoundsEnabled(true);
                objectMap = new GameObject[height][width];
                return GameScene.this;
            }
        });

        levelLoader.registerEntityLoader(TAG_ENTITY, new IEntityLoader()
        {
            @Override
            public IEntity onLoadEntity(final String pEntityName, final Attributes pAttributes)
            {
                final int x = SAXUtils.getIntAttributeOrThrow(pAttributes, TAG_ENTITY_ATTRIBUTE_X);
                final int y = SAXUtils.getIntAttributeOrThrow(pAttributes, TAG_ENTITY_ATTRIBUTE_Y);
                final String type = SAXUtils.getAttributeOrThrow(pAttributes,
                        TAG_ENTITY_ATTRIBUTE_TYPE);

                final GameObject levelObject;

                if (type.equals(TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_JEWEL))
                {
                    levelObject = getScene().makeJewel(x, y);
                }
                else if (type.equals(TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_GROUND))
                {
                    levelObject = getScene().makeGround(x, y);
                }
                else if (type.equals(TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_WALL))
                {
                    levelObject = getScene().makeWall(x, y);
                }
                else if (type.equals(TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_STONE))
                {
                    levelObject = getScene().makeStone(x, y);
                }
                else if (type.equals(TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_PLAYER))
                {
                    player = getScene().makePlayer(x, y);
                    levelObject = player;
                }
                else if (type.equals(TAG_ENTITY_ATTRIBUTE_TYPE_VALUE_ENEMY))
                {
                    levelObject = getScene().makeEnemy(x, y);
                }
                else
                {
                    throw new IllegalArgumentException();
                }

                levelObject.setCullingEnabled(true);
                objectMap[y][x] = levelObject;
                return levelObject;
            }
        });

        try
        {
            levelLoader.loadLevelFromAsset(activity.getAssets(), levelNumber + ".lvl");
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    private void gameOver()
    {
    	this.setChildrenIgnoreUpdate(true);
        resourcesManager.player_death.play();
        
        this.registerUpdateHandler(new TimerHandler(0.2f, new ITimerCallback() 
        {
            public void onTimePassed(final TimerHandler pTimerHandler) 
            {
            	engine.unregisterUpdateHandler(pTimerHandler);

            	disposeLevel();
            	SceneManager.getInstance().loadGameOverScene(engine);
            }
        }));
    }

    private void completeLevel()
    {
    	this.setChildrenIgnoreUpdate(true);
        resourcesManager.level_complete.play();
    	this.registerUpdateHandler(new TimerHandler(0.2f, new ITimerCallback() 
    	{
    		public void onTimePassed(final TimerHandler pTimerHandler) 
    		{
    			engine.unregisterUpdateHandler(pTimerHandler);
    			
    			disposeLevel();
    			SceneManager.getInstance().loadLevelCompleteScene(engine);
    		}
    	}));

        int currentProgress = prefrences.getInt("level", 1);
        if (level < currentProgress) return;

        // save progress
        Editor ed = prefrences.edit();
        ed.putInt("level", level + 1);
        ed.commit();
    }

    public void onObjectMoved(int oldX, int oldY, int newX, int newY)
    {
        GameObject object = objectMap[oldY][oldX];
        objectMap[oldY][oldX] = null;
        objectMap[newY][newX] = object;
    }

    protected GameScene getScene()
    {
        return this;
    }

    protected GameObject makeWall(int x, int y)
    {
        return new GameObject(getScene(), GameObject.Type.WALL, x, y, 
        		resourcesManager.wall_region, vbom);
    }

    protected Player makePlayer(int x, int y)
    {
        return new Player(getScene(), x, y, vbom, camera)
        {
            @Override
            public void onDie()
            {
                gameOver();
            }
        };
    }

    protected GameObject makeStone(int x, int y)
    {
        return new FallingGameObject(getScene(), GameObject.Type.STONE, x, y,
                resourcesManager.stone_region, vbom)
        {
            @Override
            protected void onManagedUpdate(float pSecondsElapsed)
            {
                super.onManagedUpdate(pSecondsElapsed);
                this.fall();
            }
            
            @Override
            protected void playFallSound()
            {
                super.playFallSound();
                resourcesManager.stone_fall.play();
            }
        };
    }

    protected GameObject makeGround(int x, int y)
    {
        return new GameObject(getScene(), GameObject.Type.GROUND, x, y,
                resourcesManager.ground_region, vbom)
        {
            @Override
            public void onPlayerContact()
            {
                this.setVisible(false);
                this.setIgnoreUpdate(true);
            }
        };
    }

    protected GameObject makeJewel(int x, int y)
    {
        ++jewelsLeft;
        return new FallingGameObject(getScene(), GameObject.Type.JEWEL, x, y,
                resourcesManager.jewel_region, vbom)
        {
            @Override
            public void onPlayerContact()
            {
            	resourcesManager.pick_jewel.play();
            	if (objectMap[cellY() + 1][cellX()] == this)
            	{
            		objectMap[cellY() + 1][cellX()] = null;
            	}
                --jewelsLeft;
                this.setVisible(false);
                this.setIgnoreUpdate(true);

                if (jewelsLeft == 0)
                {
                    completeLevel();
                }
            }

            @Override
            protected void onManagedUpdate(float pSecondsElapsed)
            {
                super.onManagedUpdate(pSecondsElapsed);
                if (!this.isInSameCell(player))
                {
                    this.fall();
                }
            }
            
            @Override
            protected void playFallSound()
            {
                super.playFallSound();
            	resourcesManager.jewel_fall.play();
            }
        };
    }

    protected Enemy makeEnemy(int x, int y)
    {
        return new Enemy(getScene(), x, y, vbom, camera)
        {
            @Override
            public void onPlayerContact()
            {
                super.onPlayerContact();
                player.onDie();
            }
        };
    }
}
